// DynlinkTest.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <windows.h>

// Some custom function to put into the stub
#include <psapix.h>


#ifndef DYNLINK_LINK_IMPLICIT
	// Loading under a different name only works with explicit loading!
	// Need to make sure the file is actually included again (the usual protection)
	// you may have to check this for you own library
#	define DYNLINK_PREFIX S_
#	include <psapixx.h>
	bool g_bImplicit = false;
#else
	// Keep some runtime info about whether we are explicitely or implicitely 
	// linked.
	bool g_bImplicit = true;
#endif

// for easy access to the error proxy.
#define _E(symbol) \
	CDynlinkGetErr(symbol)

// test whether an expected error really occurred, and output as expected error
void TEST_ERR(const CDynlinkBase &obj, DWORD dwErr)
{
	std::basic_string<TCHAR> s_err;
	bool b_ok;

	if (dwErr != (DWORD) -1)
	{
		b_ok = dwErr == obj.get_error(s_err);
	}
	else
	{
		b_ok = ERROR_SUCCESS != obj.get_error(s_err);
	}
	
	if (b_ok)
	{
		_tprintf(_T("  (ok): Object returned expected error code (%d)\n"), dwErr);
	}
	else
	{
		throw std::exception("  ERROR: Wrong error code.\n");
	}

	if (dwErr != ERROR_SUCCESS)
	{
		_tprintf(_T("        Error string: %s\n"), s_err.c_str());
	}
}
	
void TEST_ERR(const CDynlinkBase * p_obj, DWORD dwErr)
{
	TEST_ERR(*p_obj, dwErr);
}
	
void test_dynlink()
{
	int ret_load;
	_tprintf(_T("--TEST (dynlink): Loading the library\r\n"));
	std::basic_string<TCHAR> s_fn, s_err;

	// Load the dll under a wrong name. This will always fail (see below)
	ret_load = dynlink_psapi().load(_T("wrongname"));

	if (g_bImplicit)
	{
		// The mode should be implicit linking.
		TEST(dynlink_psapi().get_mode() == CDynlinkLib::imp)
		// The return value should indicate that loading under a different name
		// than the original is not allowed for this method.
		TEST(ret_load == DYNLINK_ERR_INVALID_METHOD);
		// get_error() will return nothing useful
		TEST(dynlink_psapi().get_error() == 0)
	}
	else
	{
		// The return value will indicate that LoadLibrary has not worked
		TEST(ret_load == DYNLINK_ERR_LOADLIBRARY)
		// The windows error should be ERROR_MOD_NOT_FOUND
		TEST_ERR(dynlink_psapi(), ERROR_MOD_NOT_FOUND);
		// The mode should still be set to ex (because DYNLINK_USE_STUBS is not
		// defined in psapix.h
		TEST(dynlink_psapi().get_mode() == CDynlinkLib::ex)
	}

	// Load with the default name. This should succeed in both cases
	ret_load = dynlink_psapi().load();
	TEST(ERROR_SUCCESS == dynlink_psapi().get_error());
	
	if (g_bImplicit)
	{
		// Should be successful
		TEST(ret_load == DYNLINK_SUCCESS);
	}
	else
	{
		// Should indicate that we have some null pointers because we have 
		// added GetProcessImageFileNameW1() to the header which does not
		// exist in the dll
		TEST(ret_load == DYNLINK_ERR_NULLPTRS);
	}

	_tprintf(_T("--TEST (dynlink): get_filename()\r\n"));
	// Read out the actual path to the module loaded
	TEST(dynlink_psapi().get_filename(s_fn));
	_tprintf(_T("  --->: dll path: %s\n"), s_fn.c_str());


	_tprintf(_T("--TEST (dynlink): useing dll symbols\r\n"));
	// Use a function from the module.
	TCHAR sName1[1024];
	TEST(GetProcessImageFileName(GetCurrentProcess(), sName1, 1024) > 0);
	_tprintf(_T("  --->: exe path: %s\r\n"), sName1);

	// All is good for a symbol that is part of the library. get_error() will
	// return ERROR_SUCCESS for both explicit and implicit linking.
	TEST(GetProcessImageFileNameW != NULL);
	TEST(_E(GetProcessImageFileNameW).get_error() == ERROR_SUCCESS);
	TEST(_E(GetProcessImageFileNameW).is_loaded());
	
	// REMARK:
	// The following would lead to a compile-time error when 
	// DYNLINK_LINK_IMPLICIT is defined as the symbol name is not wrapped by
	// CDynlinkGetErr (the _E() macro) and, of course, function pointers 
	// have no member function called get_error()
#ifndef DYNLINK_LINK_IMPLICIT
	TEST(GetProcessImageFileNameW.get_error() == ERROR_SUCCESS);
#endif

	_tprintf(_T("--TEST (dynlink): throw when not loaded\r\n"));		
	// Ask the library object to throw when we reference a function not 
	// loaded and not stub is in place.
	dynlink_psapi().set_throw(1);

	try
	{
		// This would lead to a link-time error when DYNLINK_LINK_IMPLICIT is 
		// defined as the function GetProcessImageFileNameW1() is only in the
		// header file, not in the import library.
#		ifndef DYNLINK_LINK_IMPLICIT
			WCHAR sName[1024];
			GetProcessImageFileNameW1(GetCurrentProcess(), sName, 1024);
			// We expect an exception to be thrown, so this should never be
			// reached
			TEST(0);
#		endif
	}
	catch(CDynlinkSymbol * p_sym)
	{
		// We expect to end up here.
		// We can use the pointer to the symbol to get extended error 
		// information like ...

		// ... the symbol that caused the error
		printf("  (ok): Caught expected exception, symbol '%s'\r\n", p_sym ->get_name());
		// ... which library it was supposed to be loaded from and its location
		// on disk
		std::basic_string<TCHAR> str;
		p_sym ->get_lib() ->get_filename(str);
		_tprintf(_T("        Not loaded from image at '%s'\r\n"), str.c_str());
		
		// Check whether the exception indeed occurred because there was no 
		// symbol loaded ...
		TEST(p_sym ->is_loaded_or_stub() == false);
		// ... and print the error
		TEST_ERR(p_sym, (DWORD)-1);
	}

	// Stop throwing exceptions
	dynlink_psapi().set_throw(0);

#ifndef DYNLINK_LINK_IMPLICIT
	_tprintf(_T("--TEST (dynlink): unavailable symbols\r\n"));
	// In explicit linking we have the two additional declarations in the header
	// which refer to symbols not in the library. 
	// They will remain NULL.

	// This code leads to link time errors when linking implicitely (which is
	// logical as then, it is clear already at link time that the symbols are
	// unavialable)
	TEST(!_E(GetProcessImageFileNameW1).is_loaded());
	TEST(GetProcessImageFileNameW1 == NULL);
	TEST_ERR(GetProcessImageFileNameW1, ERROR_PROC_NOT_FOUND);

	_tprintf(_T("--TEST (dynlink): free() and auto_load()\r\n"));

	// Test unloading and auto-load
	TEST(dynlink_psapi().free());
	TEST(dynlink_psapi().get_state() == DYNLINK_ERR_NOTLOADED);

	// The symbols are not loaded but no error occurred!
	TEST(GetProcessImageFileName.is_loaded_or_stub() == false);
	TEST_ERR(GetProcessImageFileNameW, ERROR_SUCCESS);

	// Set aut_load to true 
	dynlink_psapi().set_auto_load(true);
	// Trigger by accessing the symbol as a pointer (and check whether it 
	// worked as the pointer is NULL otherwise)
	TEST(GetProcessImageFileNameW != NULL);
	// And check again, whether it worked
	TEST(GetProcessImageFileName.is_loaded() == true);

	_tprintf(_T("--TEST (dynlink): loading prefixed library copy\r\n"));
	// Demonstrate prefixed loading and loading a second copy of the same
	// library.
	// When loading the 'wrong' image for the architecture we will get the 
	// appropriate error.
#ifdef _WIN64
	TEST(dynlink_S_psapi().load(_T("psapi_copy_win32")) == DYNLINK_ERR_LOADLIBRARY);
	TEST_ERR(dynlink_S_psapi(), ERROR_BAD_EXE_FORMAT);
	TEST(dynlink_S_psapi().load(_T("psapi_copy_x64")) == DYNLINK_ERR_STUBS);
#else
	TEST(dynlink_S_psapi().load(_T("psapi_copy_x64")) == DYNLINK_ERR_LOADLIBRARY);
	TEST_ERR(dynlink_S_psapi(), ERROR_BAD_EXE_FORMAT);
	TEST(dynlink_S_psapi().load(_T("psapi_copy_win32")) == DYNLINK_ERR_STUBS);
#endif

	_tprintf(_T("--TEST (dynlink): calling stubs\r\n"));
	// The stubs usually will return the DYNLINK_RETVAL_DWORD defined above

	TEST(S_GetProcessImageFileNameW1(NULL, 0, 0) == (DWORD)-1);
	TEST(S_GetProcessImageFileNameW2(NULL, 0, 0) == -1);
	// .. or the custom value that has been set in the macro
	TEST(S_GetProcessImageFileNameW3(NULL, 0, 0) == (DWORD)-2);

	// Read out the filename of the dll that has actually been loaded
	dynlink_S_psapi().get_filename(s_fn);
	_tprintf(_T("  --->: dll path: %s\n"), s_fn.c_str());

	// Test unload and load stubs only
	dynlink_S_psapi().load_stubs();
	// The original function is no longer called and the stub value should be
	// returned
	TEST(S_GetProcessImageFileNameW(NULL, 0, 0)  == (DWORD)-1);
	// Free the library, upon next use it should be successfully autoloaded 
	// (not the copy though)
	dynlink_S_psapi().free();
	// Re-auto-load and check whether old stubs still functional
	TEST(S_GetProcessImageFileNameW3(NULL, 0, 0) == (DWORD)-2);

	_tprintf(_T("--TEST (dynlink): using prefixed symbols\r\n"));
	// REMARK: 
	// S_GetProcessImageFileName will not work as the #define in the header 
	// file was not modified correctly in psapixx.h. Of course this could be 
	// done by using the DYNLINK_PREFIX inside the header to define the TCHAR
	// functions if absolutely necessary.
	TCHAR sName2[1024];
#ifdef UNICODE
	TEST(S_GetProcessImageFileNameW(GetCurrentProcess(), sName2, 1024) > 0);
#else
	TEST(S_GetProcessImageFileNameA(GetCurrentProcess(), sName2, 1024) > 0);
#endif
	TEST(!_tcscmp(sName1, sName2));
	_tprintf(_T("  --->: exe path: %s\r\n"), sName2);

	// Get the filename loaded for the prefixed dll
	_tprintf(_T("--TEST (dynlink): get_filename() of dll loaded with prefix\r\n"));
	dynlink_S_psapi().get_filename(s_fn);
	_tprintf(_T("  --->: dll path: %s\n"), s_fn.c_str());
#endif
	_tprintf(_T("SUCCESS: ALL TESTS PASSED\r\n\r\n"));

}